#include "D3D11Pad.h"
#include "Scene.h"

#pragma region static
ID3D11InputLayout *D3D11Text::IL_TextVertex = NULL;
ID3D11Buffer
	*D3D11Text::VB = NULL,
	*D3D11Text::IB = NULL,
	*D3D11Text::cb_COLOR = NULL;
ID3D11VertexShader *D3D11Text::VS_Fill_Font = NULL;
ID3D11PixelShader
	*D3D11Text::PS_Fill = NULL,
	*D3D11Text::PS_Font = NULL;

void D3D11Text::GlobalInit() {
//shaders:
	HRESULT hr;
	ID3DBlob *SBlob = NULL, *EBlob = NULL;

	D3D11_INPUT_ELEMENT_DESC ldesc[ ] = {
		{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 } };

	CompileFromFile( "Modules\\D3D11Shaders\\D3D11Text.fx", NULL, NULL, "VS_Fill_Font", vs_ver, ShaderCompileFlag, 0, NULL, &SBlob, &EBlob, &hr );
	ShowShaderCompilationError( hr, EBlob );
	HR( gd3dDevice->CreateInputLayout( ldesc, 2, SBlob->GetBufferPointer(), SBlob->GetBufferSize(), &IL_TextVertex ) );
	HR( gd3dDevice->CreateVertexShader( SBlob->GetBufferPointer(), SBlob->GetBufferSize(), NULL, &VS_Fill_Font ) );
	REL( SBlob );
	REL( EBlob );

	CompileFromFile( "Modules\\D3D11Shaders\\D3D11Text.fx", NULL, NULL, "PS_Fill", ps_ver, ShaderCompileFlag, 0, NULL, &SBlob, &EBlob, &hr );
	ShowShaderCompilationError( hr, EBlob );
	HR( gd3dDevice->CreatePixelShader( SBlob->GetBufferPointer(), SBlob->GetBufferSize(), NULL, &PS_Fill ) );
	REL( SBlob );
	REL( EBlob );

	CompileFromFile( "Modules\\D3D11Shaders\\D3D11Text.fx", NULL, NULL, "PS_Font", ps_ver, ShaderCompileFlag, 0, NULL, &SBlob, &EBlob, &hr );
	ShowShaderCompilationError( hr, EBlob );
	HR( gd3dDevice->CreatePixelShader( SBlob->GetBufferPointer(), SBlob->GetBufferSize(), NULL, &PS_Font ) );
	REL( SBlob );
	REL( EBlob );

//buffers:
	D3D11_BUFFER_DESC bdesc;
	D3D11_SUBRESOURCE_DATA sdata;

	ZeroMemory( &bdesc, sizeof(bdesc) );
	bdesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	bdesc.ByteWidth = 4*258*16;//16512 > 15936
	bdesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	bdesc.MiscFlags = 0;
	bdesc.StructureByteStride = 0;
	bdesc.Usage = D3D11_USAGE_DYNAMIC;

	HR( gd3dDevice->CreateBuffer( &bdesc, NULL, &VB ) );

	WORD idx[6*258];

	for( UINT i = 0, k = 0; i < 256; i++ ) {
		int j = i*4;
		idx[k] = j;			k++;
		idx[k] = j+1;		k++;
		idx[k] = j+2;		k++;
		idx[k] = j;			k++;
		idx[k] = j+2;		k++;
		idx[k] = j+3;		k++;
	}
	
	ZeroMemory( &bdesc, sizeof(bdesc) );
	bdesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	bdesc.ByteWidth = 6*258*2;
	bdesc.CPUAccessFlags = 0;
	bdesc.MiscFlags = 0;
	bdesc.StructureByteStride = 0;
	bdesc.Usage = D3D11_USAGE_IMMUTABLE;
	
	ZeroMemory( &sdata, sizeof(sdata) );
	sdata.pSysMem = idx;

	HR( gd3dDevice->CreateBuffer( &bdesc, &sdata, &IB ) );

	ZeroMemory( &bdesc, sizeof(bdesc) );
	bdesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	bdesc.ByteWidth = 32;
	bdesc.CPUAccessFlags = 0;
	bdesc.MiscFlags = 0;
	bdesc.StructureByteStride = 0;
	bdesc.Usage = D3D11_USAGE_DEFAULT;

	HR( gd3dDevice->CreateBuffer( &bdesc, nullptr, &cb_COLOR ) );
}

void D3D11Text::GlobalExit() {
	REL( IL_TextVertex );
	REL( VS_Fill_Font );
	REL( PS_Fill );
	REL( PS_Font );
	REL( VB );
	REL( IB );
	REL( cb_COLOR );
}

#pragma endregion

//==========================================================================
//								D3D11Text
//==========================================================================

D3D11Text::D3D11Text() {
	Buffer = new char [512];
	CharSet = ANSI_CHARSET;
	sharing = spacing = 0;
	rotation = 0.0f;
	HAlign = VAlign = 0;
	Abc = NULL;
	Data = NULL;

	Surf = NULL;
	Tex = NULL;
	SRV = NULL;

	red = green = blue = alpha = 1.0f;
	max = 0;
}

D3D11Text::~D3D11Text() {
	if( Buffer ) {
		delete Buffer;
		Buffer = NULL;
	}
	if( Data ) {
		delete Data;
		Data = NULL;
	}
	if( Abc ) {
		delete Abc;
		Abc = NULL;
	}
	REL( Surf );
	REL( Tex );
	REL( SRV );
}

void D3D11Text::SetCharSet( int _CharSet ) {
	CharSet = _CharSet;
}

void D3D11Text::SetTextSpace( int space ) {
	spacing = (tm.tmHeight*space)/100;
}

void D3D11Text::SetTextHAlign( int x ) {
	HAlign = x;
}

void D3D11Text::SetTextVAlign( int x ) {
	VAlign = x;
}

void D3D11Text::SetTextShare( int percent ) {
	sharing = (tm.tmHeight*percent)/100;
}

void D3D11Text::SetLineSpace( int percent ) {
	linespacing = tm.tmHeight + (tm.tmHeight*percent)/100;
}

int D3D11Text::GetLineSpace() {
	return linespacing;
}

bool D3D11Text::Init( int size, char *fontname, int weight, int last ) {
	// Create A Font
	//
	LOGFONTA fnt;

	memset( (void *)&fnt, 0, sizeof(LOGFONTA) );

	fnt.lfHeight			= size;
	fnt.lfWidth				= 0;
	fnt.lfEscapement		= 0;
	fnt.lfOrientation		= 0;
	fnt.lfWeight			= weight;
	fnt.lfItalic			= false;
	fnt.lfUnderline			= false;
	fnt.lfStrikeOut			= false;
	fnt.lfCharSet			= CharSet;
	fnt.lfOutPrecision		= OUT_DEFAULT_PRECIS;
	fnt.lfClipPrecision		= CLIP_DEFAULT_PRECIS;
	fnt.lfQuality			= ANTIALIASED_QUALITY;
	fnt.lfPitchAndFamily	= DEFAULT_PITCH | FF_MODERN;

	if( fontname )
		strncpy_s( fnt.lfFaceName, 31, fontname, 30 );

	return Init(&fnt, last);
}

bool D3D11Text::Init( int size, int Style, int weight, int last ) {
	// Create A Font
	//
	LOGFONTA fnt;

	memset((void *)&fnt, 0, sizeof(LOGFONTA));

	fnt.lfHeight		 = size;
	fnt.lfWidth			 = 0;
	fnt.lfEscapement	 = 0;
	fnt.lfOrientation    = 0;
	fnt.lfWeight		 = weight;
	fnt.lfItalic		 = false;
	fnt.lfUnderline		 = false;
	fnt.lfStrikeOut		 = false;
	fnt.lfCharSet		 = CharSet;
	fnt.lfOutPrecision	 = OUT_DEFAULT_PRECIS;
	fnt.lfClipPrecision	 = CLIP_DEFAULT_PRECIS;
	fnt.lfQuality		 = ANTIALIASED_QUALITY;
	fnt.lfPitchAndFamily = Style;
 
	return Init( &fnt, last );
}

bool D3D11Text::Init( LOGFONTA *fnt, int final ) {
	HFONT hF = CreateFontIndirectA(fnt);
	return Init( hF, final );
}

bool D3D11Text::Init( HFONT hFont, int final ) {
	if( !hFont ) {
		oapiWriteLog( "D3D11Text::Init() NULL hFont" );
		return false;
	}

	tex_w = 512;
	tex_h = 128;
	last = final;

	int first = 33;		//ANSI code of the first charter

	// Allocate space for data
	//
	Data = new D3D11FontData [last];
	memset( Data, 0, last*sizeof(D3D11FontData) );

	Abc = new _ABC [last];
	memset( Abc, 0, last*sizeof(_ABC) );

	bool bFirst = true;

restart:

	if( tex_h >= 2048 )
		return false;

	D3D11_TEXTURE2D_DESC tdesc;
	ZeroMemory( &tdesc, sizeof(tdesc) );
	tdesc.ArraySize = 1;
	tdesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
	tdesc.CPUAccessFlags = 0;
	tdesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	tdesc.Height = tex_h;
	tdesc.MipLevels = 1;
	tdesc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;
	tdesc.SampleDesc.Count = 1;
	tdesc.Usage = D3D11_USAGE_DEFAULT;
	tdesc.Width = tex_w;

	HR( gd3dDevice->CreateTexture2D( &tdesc, NULL, &Tex ) );
#if _DEBUG
	{
		char buffer[200];
		buffer[199] = '\0';
		sprintf(buffer, "[D3D11Text::Init]%p", hFont);
		if (Tex)
			Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
		if (SRV)
			SRV->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
	}
#endif
	HR( Tex->QueryInterface( __uuidof(IDXGISurface1), (void**)&Surf ) ); 

	HDC hDC = NULL;
	if( Surf->GetDC( FALSE, &hDC ) != S_OK ) {
		oapiWriteLog( "D3D11Text::Init() - GetDC() failed." );
		return false;
	}

	HFONT hOld = (HFONT)SelectObject( hDC, hFont );
	if( !hOld ) {
		oapiWriteLog( "D3D11Text::Init() - SelectObject() failed." );
		return false;
	}

	if( bFirst ) {
		// Get Char Width/Spacing information from 33 to 255
		//
		if( !GetCharABCWidthsA( hDC, first, last, Abc ) )
			oapiWriteLog( "D3D11Text::Init() - GetCharABCWidths() failed." );

		// Get Text Metrics information
		//
		memset( &tm, 0, sizeof(TEXTMETRIC) );

		if( !GetTextMetricsA( hDC, &tm ) ) {
			oapiWriteLog( "D3D11Text::Init() - GetTextMetrics() failed." );
			return false;
		}
		bFirst = false;
	}

	// Draw Characters
	//
	char text[4];
	text[1] = 0;

	int
		s = tm.tmMaxCharWidth,
		a = tm.tmAscent + 1,
		d = tm.tmDescent + 1,
		h = a + d,
		x = 5,
		y = 5 + h,
		c = 0;

	SIZE fnts;

	SetTextAlign( hDC, TA_BASELINE | TA_LEFT );
	SetTextColor( hDC, COLORREF(0xFFFFFF) );
	SetBkColor( hDC, 0 );
	SetBkMode( hDC, TRANSPARENT );

	float
		tw = 1.0f/float(tex_w),
		th = 1.0f/float(tex_h);

	while( c < last ) {
		text[0] = c;
		text[1] = 0;

		TextOutA( hDC, x, y, text, 1 );
		GetTextExtentPoint32A( hDC, text, 1, &fnts );

		Data[c].sp = float(fnts.cx);
		Data[c].w = float(fnts.cx+2);
		Data[c].h = float(h);

		Data[c].tx0 = float(x-1);
		Data[c].tx1 = float(x - 1 + fnts.cx + 2);

		Data[c].ty0 = float(y - a);
		Data[c].ty1 = float(y + d);

		c++;	//next charter

		x += (fnts.cx + 4);		// --!!-- In order to increase spacing between charters increase this --!!--

		if( (x+s) >= tex_w ) {	//Starting a new line
			x = 5;
			y += (h+3);
		}

		if( (y+h) >= tex_h ) {
			Surf->ReleaseDC( NULL );
			REL( Surf );
			REL( Tex );
			tex_h *= 2;
			goto restart;
		}
	}

	SelectObject( hDC, hOld );
	Surf->ReleaseDC( NULL );

	DeleteObject( hFont );

	SetLineSpace( 0 );
	SetTextShare( 0 );
	SetTextSpace( 0 );
	
	D3D11_SHADER_RESOURCE_VIEW_DESC SDesc;
	ZeroMemory( &SDesc, sizeof( SDesc ));
	SDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	SDesc.Texture2D.MipLevels = 1;
	SDesc.Texture2D.MostDetailedMip = 0;
	SDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;

	HR( gd3dDevice->CreateShaderResourceView( Tex, &SDesc, &SRV ) );

	return true;
}

void D3D11Text::SetColor( DWORD c ) {
	alpha	=	( (float)((c >> 24) & 0xFF) ) / 255.0f;
	red		=	( (float)((c >> 16) & 0xFF) ) / 255.0f;
	green	=	( (float)((c >> 8) & 0xFF) ) / 255.0f;
	blue	=	( (float)(c & 0xFF)) / 255.0f;
}

void D3D11Text::SetColor( float _red, float _green, float _blue, float _alpha ) {
	red = _red;
	green = _green;
	blue = _blue;
	alpha = _alpha;
}

void D3D11Text::Reset() {
	max = 0;
}

float D3D11Text::Width() {
	return max;
}

void D3D11Text::SetRotation( float deg ) {
	rotation = deg;
}

float D3D11Text::Length( const char *format, ... ) {
	va_list args;
	va_start( args, format );

	int count = _vsnprintf_s( (char*)Buffer, 512, 512, format, args );
	va_end( args );

	return Length2( Buffer, count );
}

float D3D11Text::Length2( const char *str, int le ) {
	float LEN = 0.0f;
	float spe = float(spacing);

	if( le == 0 || le == -1 )
		le = strlen( str );

	for( int j = 0; j < le; j++ ) {
		unsigned char c = str[j];
		if( c <= last )
			LEN += (Data[c].sp + spe);
	}

	LEN = LEN - spe;
	if( LEN < 0 )
		LEN = 0;
	return LEN;
}

float D3D11Text::Length( char c ) {
	return Data[c].sp + float(spacing);
}

float D3D11Text::Print( D3DXCOLOR *color, int x, int y, const char *str, int len, D3DXMATRIX *pVP, D3DXCOLOR *bbox ) {
	float
		length = 0.0f,
		xpos = float(x),
		ypos = float(y),
		x_orig = xpos;

	if( !len || !str )
		return 0.0f;

	if( len == -1 )
		len = strlen( str );

	if( len > 250 ) {
		oapiWriteLog( "D3D11Text - buffer overload" );
		return 0.0f;
	}

	if( HAlign == 1 )
		xpos -= Length2( str, len )*0.5f;
	if( HAlign == 2 )
		xpos -= Length2( str, len );
	if( VAlign == 1 )
		ypos -= tm.tmAscent;
	if( VAlign == 2 )
		ypos -= tm.tmHeight;

	float h = Data[0].h;

	xpos = ceil( xpos );
	ypos = ceil( ypos );

	xpos -= 0.5f;
	ypos -= 0.5f;

	TEXTVERTEX *Vtx = new TEXTVERTEX [4*(len+1)], *vtx;
	vtx = Vtx;

	float
		bbox_l = xpos,
		bbox_t = ypos+1,
		bbox_b = ypos+h-1;

	for( int j = 0; j < len; j++ ) {
		unsigned char c = str[j];

		float w = Data[c].w;

		vtx[0].x = vtx[3].x = xpos;
		vtx[0].y = vtx[1].y = ypos;
		vtx[0].tu = vtx[3].tu = Data[c].tx0/(float)tex_w;
		vtx[0].tv = vtx[1].tv = Data[c].ty0/(float)tex_h;
		vtx[1].x = vtx[2].x = xpos+w;
		vtx[1].tu = vtx[2].tu = Data[c].tx1/(float)tex_w;
		vtx[2].y = vtx[3].y = ypos+h;
		vtx[2].tv = vtx[3].tv = Data[c].ty1/(float)tex_h;
		vtx += 4;
		xpos += (Data[c].sp + float(spacing));
	}

	float bbox_r = xpos + 1;

	const UINT TextVertexStride = 16, VBOffset = 0;
	D3DXMATRIX rot, out;	
	if( fabs( rotation ) > 1e-3 ) {
		D3DXMatrixTransformation2D( &rot, NULL, 0.0f, NULL, &D3DXVECTOR2( (bbox_l + bbox_r)*0.5f, bbox_t ), -rotation*0.01745329f, NULL );
		D3DXMatrixMultiply( &out, &rot, pVP );
		iCtx->UpdateSubresource( cb_D3DXMATRIX_x1, 0, NULL, &out, 0, 0 );
	}
	else
		iCtx->UpdateSubresource( cb_D3DXMATRIX_x1, 0, NULL, pVP, 0, 0 );

	iCtx->IASetPrimitiveTopology( D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
	iCtx->IASetInputLayout( IL_TextVertex );
	iCtx->VSSetShader( VS_Fill_Font, NULL, NULL );
	iCtx->PSSetShader( PS_Font, NULL, NULL );
	iCtx->VSSetConstantBuffers( 0, 1, &cb_D3DXMATRIX_x1 );
	iCtx->IASetVertexBuffers( 0, 1, &VB, &TextVertexStride, &VBOffset );
	iCtx->IASetIndexBuffer( IB, DXGI_FORMAT_R16_UINT, 0 );
	iCtx->RSSetState( RS_CullNone_Solid );

	iCtx->OMSetBlendState( BS_SrcAlpha, D3DXCOLOR( 1, 1, 1, 1 ), 0xFFFFFFFF );
	iCtx->OMSetDepthStencilState( DSS_NoDepth_NoStencil, 0 );

	D3D11_MAPPED_SUBRESOURCE SRes;
	iCtx->PSSetConstantBuffers( 1, 1, &cb_D3DXVECTOR4 );
	if( bbox ) {
		TEXTVERTEX _Vtx[4] = {
			bbox_l, bbox_t, 0.0f, 0.0f,
			bbox_l, bbox_b, 0.0f, 0.0f,
			bbox_r, bbox_b, 0.0f, 0.0f,
			bbox_r, bbox_t, 0.0f, 0.0f
		};

		ZeroMemory( &SRes, sizeof(SRes) );
		iCtx->Map( VB, 0, D3D11_MAP_WRITE_DISCARD, 0, &SRes );
		memcpy( SRes.pData, _Vtx, 64 );
		iCtx->Unmap( VB, 0 );

		iCtx->UpdateSubresource( cb_D3DXVECTOR4, 0, NULL, bbox, 0, 0 );
		iCtx->PSSetShader( PS_Fill, NULL, NULL );
	//	iCtx->OMSetBlendState( BS_NoBlend, D3DXCOLOR( 1, 1, 1, 1 ), 0xFFFFFFFF );

		//render a quad filled with bbox color		
		iCtx->DrawIndexed( 6, 0, 0 );
	}

	ZeroMemory( &SRes, sizeof(SRes) );
	iCtx->Map( VB, 0, D3D11_MAP_WRITE_DISCARD, 0, &SRes );
	memcpy( SRes.pData, Vtx, 16*(len << 2) );
	iCtx->Unmap( VB, 0 );

	iCtx->IASetVertexBuffers( 0, 1, &VB, &TextVertexStride, &VBOffset );
	iCtx->PSSetShaderResources( 0, 1, &SRV );
	iCtx->PSSetSamplers( 0, 1, &SS_Point_Wrap );

	D3DXVECTOR4 c( color->r, color->g, color->b, alpha );
	iCtx->UpdateSubresource( cb_D3DXVECTOR4, 0, NULL, &c, 0, 0 );
	iCtx->PSSetShader( PS_Font, NULL, NULL );

	//draw text...
	iCtx->DrawIndexed( 3*(len<<1), 0, 0 );	

	delete [ ] Vtx;

	float l = xpos - x_orig;
	if( l > max )
		max = l;
	return l;
}
